{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "A6Cf6EtZ0zX3"
      },
      "outputs": [],
      "source": [
        "# Copyright 2019, The TensorFlow Federated Authors.\n",
        "#\n",
        "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "#      http://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License.\n",
        "import matplotlib\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import collections\n",
        "import os\n",
        "import re\n",
        "import time\n",
        "import sys\n",
        "from IPython import display\n",
        "\n",
        "if not sys.version_info \u003e= (3, 0):\n",
        "  sys.stdout.write(\"This notebook requires Python 3.x\\n\")\n",
        "  sys.exit(1)\n",
        "\n",
        "# Set this to the directory where logs from your experiments reside.\n",
        "LOG_DIR=\"/tmp/sc_paper/logs\"\n",
        "\n",
        "def set_matplotlib_params(font_size=8, params=None):\n",
        "  \"\"\"Resets matplotlib defaults to nicer defaults, esp. for publications.\n",
        "\n",
        "  Args:\n",
        "    font_size: Default font size.\n",
        "    params: A dict of other params to update, e.g. {'figure.figsize': [7, 5]}.\n",
        "  \"\"\"\n",
        "  matplotlib.rcdefaults()\n",
        "\n",
        "  # Define our own defaults:\n",
        "  default_params = {\n",
        "      'axes.labelsize': font_size,\n",
        "      'axes.titlesize': font_size,\n",
        "      'font.size': font_size,\n",
        "      'legend.fontsize': font_size,\n",
        "      'xtick.labelsize': font_size,\n",
        "      'ytick.labelsize': font_size,\n",
        "      'font.family': 'sans-serif',\n",
        "      'pdf.fonttype': 42,  # Avoid Type 3 fonts in publication plots\n",
        "      'ps.fonttype': 42,  # Avoid Type 3 fonts in publication plots\n",
        "      'legend.frameon': False,\n",
        "  }\n",
        "  if params:\n",
        "    default_params.update(params)\n",
        "  matplotlib.rcParams.update(default_params)\n",
        "\n",
        "set_matplotlib_params(10,\n",
        "    {'legend.handlelength': 3.0,\n",
        "     'figure.figsize': [6, 4],  \n",
        "     'lines.linewidth': 1.5,\n",
        "    })\n",
        "\n",
        "def get_mean_and_std(x): # x is a list of lists\n",
        "  \"\"\"Computes mean and std vectors from matrices.\n",
        "  \n",
        "  Args:\n",
        "    x: a list of lists of numbers. Inner lists must have same lengths.\n",
        "  \n",
        "  Returns:\n",
        "    A dictionary with entries 'mean' and 'std', containing lists of the mean\n",
        "    values and standard deviations, computed along axis 0.\n",
        "  \"\"\"  \n",
        "  return {'mean': np.mean(x, axis=0).tolist(), 'std': np.std(x, axis=0).tolist()}\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "l22jsUBf02AH"
      },
      "outputs": [],
      "source": [
        "def find_key_val(lines, key, num_results=1):\n",
        "  \"\"\"Get \"key=value\" values from a list of lines.\n",
        "  \n",
        "  Args:\n",
        "    lines: a list of strings containing key=value pairs\n",
        "    key: a string key to search for in key=value expressions\n",
        "    num_results: Expected number of results. This function fails if less or more\n",
        "      lines starting with \"key=\" are found\n",
        "  \"\"\"\n",
        "  lines = [l for l in lines if l.startswith(key)]    \n",
        "  assert(len(lines) == num_results)\n",
        "  if num_results == 1:\n",
        "    return lines[0].lstrip(key)\n",
        "  else:\n",
        "    return [l.lstrip(prefix) for l  in lines]\n",
        "\n",
        "def parse_logfile(f, results, abort_function):\n",
        "  \"\"\"Parses a log file from an experiment run.\n",
        "  \n",
        "  Args:\n",
        "    f: log file path\n",
        "    results: existing dictionary where results will be stored. See below for\n",
        "      details on the contents of this dictionary.\n",
        "    abort_function: function that takes the log file contents as list of lines\n",
        "      and returns true if this file - based on the contents - should be ignored.\n",
        "      This can be used to filter out files that do not match certain criteria,\n",
        "      e.g. log files that do not contain a certain configuration such as\n",
        "      \"bias=0.5\".\n",
        "      \n",
        "  Returns:\n",
        "    A new entry in results, stored in results[replica], where replica is the\n",
        "    replica number extraced from the log file. Any configuration is run\n",
        "    num_replica times, and the i-th such replica will store \"replica=i\" in the\n",
        "    log file. The contents are log file dependent:\n",
        "      \n",
        "    results[replica][learning_rate][mode]\n",
        "    for mode==iid:\n",
        "      results[learning_rate][mode]['avg'] = [...]\n",
        "      results[learning_rate][mode]['raw'][test_group] = [...]\n",
        "    for mode==sep:\n",
        "      results[learning_rate][mode]['avg'] = [...]\n",
        "      results[learning_rate][mode]['raw'][train_group][test_group] = [...]\n",
        "    for mode==sc:\n",
        "      results[learning_rate][mode]['sc']: [...] average over matrix\n",
        "      results[learning_rate][mode]['pl']: [...] average over matrix diagonal\n",
        "      results[learning_rate][mode]['raw'][train_group][test_group] = [...]\n",
        "  \"\"\"\n",
        "      \n",
        "  def get_losses(lines, num_groups, num_examples_per_day_per_group):\n",
        "    \"\"\"Extracts losses and corresponding #examples from logs. Ignore epochs.\"\"\"\n",
        "    num_examples=[[] for i in range(0, num_groups)]\n",
        "    losses=[[] for i in range(0, num_groups)]\n",
        "    r = r'^day (\\d+), group (\\d+): trained on (\\d+) examples, loss=([\\d.]+)$'\n",
        "    for l in lines:\n",
        "      m = re.search(r, l)\n",
        "      if m:\n",
        "        day = int(m.group(1))\n",
        "        group = int(m.group(2))\n",
        "        num_examples[group].append(int(day)*num_examples_per_day_per_group+int(m.group(3)))\n",
        "        losses[group].append(float(m.group(3)))\n",
        "    return num_examples, losses\n",
        "\n",
        "  def get_iid_accuracies_old(lines, num_groups, num_days, num_examples_per_day):\n",
        "    accuracies={}\n",
        "    r = r'iid\\d+: day (\\d+), group (\\d+): num_train_examples (\\d+) \\(dt=\\d+s\\): num correct: \\d+/\\d+ \\(([\\d.]+)\\)$'\n",
        "    for l in lines:\n",
        "      m=re.search(r, l)\n",
        "      if m:\n",
        "        day = int(m.group(1))\n",
        "        test_group = int(m.group(2))\n",
        "        accuracy = float(m.group(4))\n",
        "        if not test_group in accuracies:\n",
        "          accuracies[test_group] = {} # using dict, not list w/ append, so we are invariant to order in file.\n",
        "        accuracies[test_group][day] = accuracy        \n",
        "    avg_accuracies = [np.mean([accuracies[g][d] for g in range(num_groups)]) for d in range(num_days)]\n",
        "    res = {'avg': avg_accuracies}\n",
        "    res['raw'] = {}\n",
        "    for g in range(num_groups):\n",
        "      res['raw'][g] = [accuracies[g][d] for d in range(num_days)]\n",
        "    return res\n",
        "\n",
        "  def get_iid_accuracies_new(lines, num_groups, num_days, num_examples_per_day):\n",
        "    num_examples_per_day_per_group = num_examples_per_day / num_days\n",
        "    accuracies={}\n",
        "    r = r'iid (\\d+) on (\\d+): day (\\d+), group (\\d+): num_train_examples (\\d+) \\(dt=\\d+s\\): num correct: \\d+/\\d+ \\(([\\d.]+)\\)$'\n",
        "    for l in lines:\n",
        "      m=re.search(r, l)\n",
        "      if m:\n",
        "        train_group = int(m.group(1))\n",
        "        test_group = int(m.group(2))\n",
        "        day = int(m.group(3))\n",
        "        assert test_group == int(m.group(4))\n",
        "        accuracy = float(m.group(6))\n",
        "        if not train_group in accuracies:\n",
        "          accuracies[train_group] = {}\n",
        "        if not test_group in accuracies[train_group]:\n",
        "          accuracies[train_group][test_group] = {}\n",
        "        accuracies[train_group][test_group][day] = accuracy\n",
        "    avg_accuracies = [np.mean([accuracies[trg][tsg][d] for trg in range(num_groups) for tsg in range(num_groups)]) for d in range(num_days)]\n",
        "    res = {'avg': avg_accuracies}\n",
        "    res['raw']={}\n",
        "    for trg in accuracies:\n",
        "      res['raw'][trg] = {}\n",
        "      for tsg in accuracies[trg]:\n",
        "        res['raw'][trg][tsg] = [accuracies[trg][tsg][d] for d in accuracies[trg][tsg]]\n",
        "    return res\n",
        "    \n",
        "  def get_sc_accuracies(lines, num_groups, num_days, num_examples_per_day):    \n",
        "    num_examples_per_day_per_group = num_examples_per_day / num_days\n",
        "    accuracies={}\n",
        "    r = r'sc (\\d+) on (\\d+): day (\\d+), group (\\d+): num_train_examples (\\d+) \\(dt=\\d+s\\): num correct: \\d+/\\d+ \\(([\\d.]+)\\)$'\n",
        "    for l in lines:\n",
        "      m=re.search(r, l)\n",
        "      if m:\n",
        "        train_group = int(m.group(1))\n",
        "        test_group = int(m.group(2))\n",
        "        day = int(m.group(3))\n",
        "        assert test_group == int(m.group(4))\n",
        "        accuracy = float(m.group(6))\n",
        "        if not train_group in accuracies:\n",
        "          accuracies[train_group] = {}\n",
        "        if not test_group in accuracies[train_group]:\n",
        "          accuracies[train_group][test_group] = {}\n",
        "        accuracies[train_group][test_group][day] = accuracy\n",
        "    res = {'raw': {}}\n",
        "    for trg in accuracies:\n",
        "      res['raw'][trg] = {}\n",
        "      for tsg in accuracies[trg]:\n",
        "        res['raw'][trg][tsg] = [accuracies[trg][tsg][d] for d in accuracies[trg][tsg]]\n",
        "    res['sc'] = [np.mean([accuracies[trg][tsg][d] for trg in range(num_groups) for tsg in range(num_groups)]) for d in range(num_days)]\n",
        "    res['pl'] = [np.mean([accuracies[g][g][d] for g in range(num_groups)]) for d in range(num_days)]\n",
        "    return res\n",
        "  \n",
        "  def get_sep_accuracies(lines, num_groups, num_days, num_examples_per_day):    \n",
        "    num_examples_per_day_per_group = num_examples_per_day / num_days\n",
        "    accuracies={}\n",
        "    r = r'sep (\\d+) on (\\d+): day (\\d+), group (\\d+): num_train_examples (\\d+) \\(dt=\\d+s\\): num correct: \\d+/\\d+ \\(([\\d.]+)\\)$'\n",
        "    for l in lines:\n",
        "      m=re.search(r, l)\n",
        "      if m:\n",
        "        train_group = int(m.group(1))\n",
        "        test_group = int(m.group(2))\n",
        "        day = int(m.group(3))\n",
        "        assert test_group == int(m.group(4))\n",
        "        accuracy = float(m.group(6))\n",
        "        if not train_group in accuracies:\n",
        "          accuracies[train_group] = {}\n",
        "        if not test_group in accuracies[train_group]:\n",
        "          accuracies[train_group][test_group] = {}\n",
        "        accuracies[train_group][test_group][day] = accuracy        \n",
        "    res = {'raw': {}}\n",
        "    for trg in accuracies:\n",
        "      res['raw'][trg] = {}\n",
        "      for tsg in accuracies[trg]:\n",
        "        res['raw'][trg][tsg] = [accuracies[trg][tsg][d] for d in accuracies[trg][tsg]]\n",
        "    res['avg'] = [np.mean([accuracies[g][g][d] for g in range(num_groups)]) for d in range(num_days)]\n",
        "    return res\n",
        "\n",
        "  # Open the log file, check for integrity, and extract the configuration from\n",
        "  # this run (learning rate, replica number, etc.).\n",
        "  with open(f) as f:\n",
        "    lines = [l.rstrip('\\n') for l in f]\n",
        "  assert lines[-1] == 'END_MARKER'\n",
        "  if abort_function(lines):\n",
        "    return\n",
        "  learning_rate=float(find_key_val(lines, 'lr='))\n",
        "  vocab_size=int(find_key_val(lines, 'vocab_size='))\n",
        "  mode=find_key_val(lines, 'mode=')\n",
        "  num_examples_per_day=int(find_key_val(lines, 'num_train_examples_per_day='))\n",
        "  num_days=int(find_key_val(lines, 'num_days='))\n",
        "  num_groups=int(find_key_val(lines, 'num_groups='))\n",
        "  replica=int(find_key_val(lines, 'replica='))\n",
        "  batch_size=int(find_key_val(lines, 'batch_size='))\n",
        "\n",
        "  # Extract the results from this run, depending on what mode was used.\n",
        "  if not replica in results:\n",
        "    results[replica] = {}\n",
        "  if not learning_rate in results[replica]:\n",
        "    results[replica][learning_rate] = {}\n",
        "  if mode == 'iid':\n",
        "    if len([l for l in lines if l.startswith('iid ')]) == 0:\n",
        "      results[replica][learning_rate][mode] = get_iid_accuracies_old(lines, num_groups, num_days, num_examples_per_day)\n",
        "    else:\n",
        "      results[replica][learning_rate][mode] = get_iid_accuracies_new(lines, num_groups, num_days, num_examples_per_day)\n",
        "  elif mode == 'sc':\n",
        "    results[replica][learning_rate][mode] = get_sc_accuracies(lines, num_groups, num_days, num_examples_per_day)\n",
        "  elif mode == 'sep':\n",
        "    results[replica][learning_rate][mode] = get_sep_accuracies(lines, num_groups, num_days, num_examples_per_day)\n",
        "  else:\n",
        "    raise ValueError('unknown mode %s' % mode)\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "mqduhInBCLXM"
      },
      "outputs": [],
      "source": [
        "# Parse log files, plot results.\n",
        "files = [f for f in os.listdir(LOG_DIR) if f.endswith('.log')]\n",
        "# Only use the results from runs that use a data bias of 0.5.\n",
        "for bias in [0.5]:\n",
        "  r = {}\n",
        "  for i, f in enumerate(files):    \n",
        "    parse_logfile(os.path.join(LOG_DIR, f), r, lambda lines: float(find_key_val(lines, 'bias='))!=bias)\n",
        "  days = range(1, len(r[0][list(r[0].keys())[0]]['iid']['avg']) + 1)\n",
        "\n",
        "  # Holds results with average and std values from across replicas.\n",
        "  ravg = {}\n",
        "  for lr in sorted(r[0]):\n",
        "    ravg[lr] = {}\n",
        "    ravg[lr]['iid'] = get_mean_and_std([r[rep][lr]['iid']['avg'] for rep in r])\n",
        "    ravg[lr]['sc'] = {}\n",
        "    ravg[lr]['sc']['sc'] = get_mean_and_std([r[rep][lr]['sc']['sc'] for rep in r])\n",
        "    ravg[lr]['sc']['pl'] = get_mean_and_std([r[rep][lr]['sc']['pl'] for rep in r])\n",
        "    ravg[lr]['sep'] = get_mean_and_std([r[rep][lr]['sep']['avg'] for rep in r])\n",
        "    num_groups = len(r[0][lr]['sc']['raw'])\n",
        "    num_days = len(r[0][lr]['sc']['raw'][0][0])\n",
        "    # plot test accuracy as a function of days, for the four different modes.\n",
        "    plt.figure(figsize=(5,3))\n",
        "    plt.title('learning_rate: %f' % lr)\n",
        "    plt.errorbar(days, ravg[lr]['iid']['mean'], ravg[lr]['iid']['std'], label='i.i.d. baseline')\n",
        "    plt.errorbar(days, ravg[lr]['sc']['sc']['mean'], ravg[lr]['sc']['sc']['std'], label='consensus model')\n",
        "    plt.errorbar(days, ravg[lr]['sc']['pl']['mean'], ravg[lr]['sc']['pl']['std'], label='pluralistic, 1 SGD chain')\n",
        "    plt.errorbar(days, ravg[lr]['sep']['mean'], ravg[lr]['sep']['std'], label='pluralistic, %d SGD chains' % num_groups)    \n",
        "    plt.legend(loc='lower right')\n",
        "    plt.ylim([0.6, 0.8])\n",
        "    plt.xlabel('day')\n",
        "    plt.ylabel('test accuracy')\n",
        "    # Plot test accuracy for the single SGD chain model on two blocks of test\n",
        "    # data.\n",
        "    plt.figure(figsize=(5, 3))\n",
        "    for t in [0, num_groups//2]:\n",
        "      cycles = get_mean_and_std([[r[rep][lr]['sc']['raw'][trg][t][d] for d in range(num_days) for trg in range(num_groups)] for rep in r])\n",
        "      plt.errorbar(np.arange(1, num_days+1, step=1/float(num_groups)), cycles['mean'], cycles['std'], label=\"test on %d:00h block\" % (t * (24//num_groups)))\n",
        "      plt.legend(loc='best')\n",
        "      plt.xlim([1,num_days])\n",
        "      plt.xlabel('day')\n",
        "      plt.ylim([0.45, 0.8])\n",
        "      plt.ylabel('test accuracy')\n",
        "\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "59xNJRTzylfO"
      },
      "outputs": [],
      "source": [
        "# Plot results again (don't re-parse log files) as used in paper. Mostly same\n",
        "# as above, but\n",
        "# - also save plots as pdfs\n",
        "# - modify one plot to include a different learning rate (see comment below and\n",
        "#   in paper).\n",
        "# - layout (skip figure title, change figure size)\n",
        "for bias in [0.5]:\n",
        "  days = range(1, len(r[0][list(r[0].keys())[0]]['iid']['avg']) + 1)\n",
        "\n",
        "  # now compute mean + std for every accuracy trace.\n",
        "  ravg = {}\n",
        "  # For the comparison of convergence, we want to use LR=0.464 for idealized,\n",
        "  # block cyclic consensus, and pluralistic single chain SGD; but for per-component\n",
        "  # SGD, we want to plot LR=1.0, because that LR works better there (due to fewer\n",
        "  # examples being processed). So we find\n",
        "  for lr in sorted(r[0], reverse=True):\n",
        "    ravg[lr] = {}\n",
        "    ravg[lr]['iid'] = get_mean_and_std([r[rep][lr]['iid']['avg'] for rep in r])\n",
        "    ravg[lr]['sc'] = {}\n",
        "    ravg[lr]['sc']['sc'] = get_mean_and_std([r[rep][lr]['sc']['sc'] for rep in r])\n",
        "    ravg[lr]['sc']['pl'] = get_mean_and_std([r[rep][lr]['sc']['pl'] for rep in r])\n",
        "    ravg[lr]['sep'] = get_mean_and_std([r[rep][lr]['sep']['avg'] for rep in r])\n",
        "    num_groups = len(r[0][lr]['sc']['raw'])\n",
        "    num_days = len(r[0][lr]['sc']['raw'][0][0])\n",
        "    # test accuracy for different modes.\n",
        "    plt.figure(figsize=(5,2.8))\n",
        "    plt.errorbar(days, ravg[lr]['iid']['mean'], ravg[lr]['iid']['std'], label='idealized i.i.d. SGD')\n",
        "    plt.errorbar(days, ravg[lr]['sc']['sc']['mean'], ravg[lr]['sc']['sc']['std'], label='block-cyclic consensus SGD')\n",
        "    plt.errorbar(days, ravg[lr]['sc']['pl']['mean'], ravg[lr]['sc']['pl']['std'], label='pluralistic single chain SGD')\n",
        "    print(lr)\n",
        "    # In the plot for LR=0.464, use the per-component SGD results for LR=1.0 instead\n",
        "    # for a fairer comparison because that LR yielded better results there.\n",
        "    if lr == 0.464:\n",
        "      plt.errorbar(days, ravg[1.0]['sep']['mean'], ravg[1.0]['sep']['std'], label='per-component SGD')    \n",
        "    else:      \n",
        "      plt.errorbar(days, ravg[lr]['sep']['mean'], ravg[lr]['sep']['std'], label='per-component SGD')    \n",
        "    plt.legend(loc='lower right', numpoints=1, fontsize=9)\n",
        "    plt.ylim([0.6, 0.78])\n",
        "    plt.xlim([1, num_days])\n",
        "    plt.xlabel('day')\n",
        "    plt.ylabel('test accuracy')\n",
        "    with open(os.path.join(LOG_DIR, \"test_lr=%f.pdf\" % lr), 'wb') as fout:\n",
        "      plt.savefig(fout, format=\"pdf\", bbox_inches='tight', transparent=True)\n",
        "    # plot cycles: from SC, how model at different times performs on same test data    \n",
        "    plt.figure(figsize=(5, 2))\n",
        "    for t in [0, num_groups//2]:\n",
        "      cycles = get_mean_and_std([[r[rep][lr]['sc']['raw'][trg][t][d] for d in range(num_days) for trg in range(num_groups)] for rep in r])\n",
        "      plt.errorbar(np.arange(1, num_days+1, step=1/float(num_groups)), cycles['mean'], cycles['std'], label=\"test on %d:00h block\" % (t * (24//num_groups)))\n",
        "      plt.legend(loc='best', numpoints=1, fontsize=9)\n",
        "      plt.xlim([1,num_days])\n",
        "      plt.xlabel('day')\n",
        "      plt.ylim([0.44, 0.82])\n",
        "      plt.ylabel('test accuracy')      \n",
        "    with open(os.path.join(LOG_DIR, \"cycles_lr=%f.pdf\" % lr), 'wb') as fout:\n",
        "      plt.savefig(fout, format=\"pdf\", bbox_inches='tight', transparent=True)\n",
        "\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "DuNMatmGyZAy"
      },
      "outputs": [],
      "source": [
        "# Show some stats about the original training data (before we preprocess\n",
        "# it by adding bias and shuffling).\n",
        "import csv\n",
        "import random\n",
        "import re\n",
        "from dateutil.parser import parse\n",
        "import datetime\n",
        "\n",
        "path = '/tmp/sc_paper/raw_data/training.1600000.processed.noemoticon.csv'\n",
        "\n",
        "\n",
        "def split_line(text):\n",
        "  return re.findall(r\"[\\w']+|[.,!?;]\", text)\n",
        "\n",
        "with open(path, errors='ignore') as csv_file:\n",
        "  csv_reader = csv.reader(csv_file, delimiter=',')\n",
        "  lines = []\n",
        "  print('reading CSV file')\n",
        "  unigrams = {}\n",
        "  i = 0\n",
        "  for row in csv_reader:\n",
        "    if row[0] == '0':\n",
        "      row[0] = 0\n",
        "    elif row[0] == '4':\n",
        "      row[0] = 1\n",
        "    else:\n",
        "      raise ValueError('Invalid label: %s' % row[0])\n",
        "    row[2] = datetime.datetime.strptime(row[2], '%a %b %d %H:%M:%S %Z %Y')\n",
        "    lines.append(row)\n",
        "    for w in split_line(row[5]):\n",
        "      if w in unigrams:\n",
        "        unigrams[w] = unigrams[w] + 1\n",
        "      else:\n",
        "        unigrams[w] = 1\n",
        "    i = i + 1\n",
        "    if i % 100000 == 0:\n",
        "      print('read %d rows' % i)\n",
        "\n",
        "# Plot sentiment as time of day\n",
        "hours_pos = [row[2].hour for row in lines if row[0] == 1]\n",
        "hours_neg = [row[2].hour for row in lines if row[0] == 0]\n",
        "h_p, b_p = np.histogram(hours_pos, bins=24, range=[0,24])\n",
        "h_n, b_n = np.histogram(hours_neg, bins=24, range=[0,24])\n",
        "plt.figure(figsize=(5, 2.5))\n",
        "plt.plot(b_p[:-1], h_p, '.-', label='positive sentiment')\n",
        "plt.plot(b_n[:-1], h_n, '.-', label='negative sentiment')\n",
        "plt.legend(loc='best', frameon=True)\n",
        "plt.xlabel('time of day')\n",
        "plt.ylabel('# posts')\n",
        "plt.xlim([0, 23])\n",
        "plt.ylim(bottom=0)\n",
        "plt.show()\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "P9LQMeSxzAGx"
      },
      "outputs": [],
      "source": [
        "# Plot stats about the data before and after adding bias, split into 6 groups\n",
        "# for every day; this is what is depicted in the Appendix of the paper.\n",
        "num_groups = 6\n",
        "# Plot sentiment as time of day\n",
        "hours_pos = [row[2].hour for row in lines if row[0] == 1]\n",
        "hours_neg = [row[2].hour for row in lines if row[0] == 0]\n",
        "h_p, b_p = np.histogram(hours_pos, bins=num_groups, range=[0,24])\n",
        "h_n, b_n = np.histogram(hours_neg, bins=num_groups, range=[0,24])\n",
        "plt.figure(figsize=(5, 2.0))\n",
        "for g in range(num_groups):\n",
        "  plt.plot([b_p[g], b_p[g]], [0, max(h_p)], '--', color='gray')\n",
        "xshift = (24/num_groups/2)\n",
        "plt.plot(b_p[:-1]+xshift, h_p, 'b.-', label='raw, positive sentiment')\n",
        "plt.plot(b_n[:-1]+xshift, h_n, 'g.-', label='raw, negative sentiment')\n",
        "\n",
        "# Plot sentiment with added bias. This formula is taken from the experiment\n",
        "# script because I don't persist the biased data (bias is added at run time for\n",
        "# added flexibility).\n",
        "bias = 0.5\n",
        "biases = np.interp(range(num_groups // 2 + 1), [0, num_groups / 2], [-bias, bias]).tolist()\n",
        "biases.extend(biases[-2:0:-1])\n",
        "h_n_biased =[0.]*num_groups\n",
        "h_p_biased =[0.]*num_groups\n",
        "\n",
        "for g in range(num_groups):\n",
        "  b = biases[g]\n",
        "  print(b)\n",
        "  if b \u003c 0:\n",
        "    # drop b*100 % negative examples\n",
        "    h_n_biased[g] = h_n[g] * (1-abs(b))\n",
        "    h_p_biased[g] = h_p[g]\n",
        "  else:\n",
        "    h_p_biased[g] = h_p[g] * (1-abs(b))\n",
        "    h_n_biased[g] = h_n[g]\n",
        "\n",
        "plt.plot(b_p[:-1]+xshift, h_p_biased, 'b.--', label='modified, positive')\n",
        "plt.plot(b_n[:-1]+xshift, h_n_biased, 'g.--', label='modified, negative')\n",
        "plt.legend(loc='lower center', ncol=2, fontsize = 8, frameon=True)\n",
        "plt.xlabel('time of day')\n",
        "plt.ylabel('# posts')\n",
        "ticks = range(0, 24, 24//num_groups)\n",
        "plt.xticks(ticks, [str(t) + \":00\" for t in ticks])\n",
        "plt.xlim([0, 24])\n",
        "plt.ylim(bottom=-10000)\n",
        "with open('/tmp/sentiment140_bias.pdf', 'wb') as f:\n",
        "  plt.savefig(f, format=\"pdf\", bbox_inches='tight', transparent=True)\n",
        "plt.show()"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "last_runtime": {
        "build_target": "",
        "kind": "local"
      },
      "name": "OSS Cyclic Analysis",
      "provenance": [
        {
          "file_id": "1G9H-HPriBD77mNj8TtkKmPswc9pKl4Bm",
          "timestamp": 1558029681690
        },
        {
          "file_id": "1TASLN5EpBp7WuIuhgJzNWmVYLQ5gRpii",
          "timestamp": 1557969917344
        },
        {
          "file_id": "1hFbtXvJjSpXv1_F4KOrqqpSo_2_aN_jp",
          "timestamp": 1547529412275
        }
      ],
      "version": "0.3.2"
    },
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.6.5"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
